package modeller.queueingnetworksmodel.structure;

import modeller.databasedesignmodel.relation.Relation;
import modeller.databasedesignmodel.servicedemand.IServiceDemand;
import modeller.databasedesignmodel.servicedemand.transaction.TransactionServiceDemand;
import modeller.databasedesignmodel.transaction.*;
import org.jdom2.Element;

import java.util.HashMap;
import java.util.HashSet;

import static queped.quepedconstants.Strings.*;

/**
 * Created by:  Jason Ye
 * Date:        30/05/2012
 * Time:        20:59
 */
public class PSServerElement extends AbstractNodeChildElement {

    HashMap<Transaction, IServiceDemand> serviceDemands;

    public PSServerElement(HashSet<Transaction> transactions, Relation relation, HashMap<Transaction, IServiceDemand> serviceDemands) {
        super(transactions, relation);
        this.serviceDemands = serviceDemands;
    }

    @Override
    public Element toXMLRepresentation() {

        Element psServer = new Element(XML_E_STATION_SECTION);

        if (relation.getRelationName().equals("Router")) {
            psServer.setAttribute(XML_A_STATION_SECTION_CLASSNAME, CLASSNAME_TUNNEL);
        } else {
            // parameters
            psServer.setAttribute(XML_A_STATION_SECTION_CLASSNAME, CLASSNAME_PSSERVER);
            // maxJobs = number of servers in station = 1
            Element maxJobs = generateMaxJobs();

            // number of visits
            Element numberOfVisits = generateNumVisitsParamElem();

            // serviceStrategy
            Element serviceStrategy = generateServiceStrategy();

            // add all to PSServerElement element
            psServer.addContent(maxJobs);
            psServer.addContent(numberOfVisits);
            psServer.addContent(serviceStrategy);

        }

        return psServer;

    }

    private Element generateServiceStrategy() {

        Element serviceStrategy = generateNewParam(JMTSERVICESTRAT, XML_A_SERVICESTRAT, true);

        boolean hasServiceTimes = false;
        // iterate through  each transaction
        for (Transaction t : transactions) {

            TransactionServiceDemand transactionServiceDemand = (TransactionServiceDemand) serviceDemands.get(t);

            double sd = transactionServiceDemand.getServiceDemandForRelation(relation);
            if (sd > 0) {
                //add ref class element
                serviceStrategy.addContent(generateNewRefClassElem(t.getTransactionName()));
                // add service time element
                serviceStrategy.addContent(generateServiceTimeStrategy(transactionServiceDemand));
            }
        }


        return serviceStrategy;

    }

    private Element generateServiceTimeStrategy(IServiceDemand transactionServiceDemand) {

        Element serviceTimeStrat = generateSubParam(JMTSERVICETIMESTRAT, XML_A_SERVICETIMESTRAT);

        serviceTimeStrat.addContent(generateExponentialSubParam());
        serviceTimeStrat.addContent(generateDistrParSubParam(transactionServiceDemand));

        return serviceTimeStrat;


    }

    private Element generateDistrParSubParam(IServiceDemand procStmtsServiceDemandList) {

        Element distrPar = generateSubParam(JMTExponentialParClasspath, XML_A_DISTRPARAM);

        Element lambda = generateSubParam(DOUBLECLASSPATH, "lambda");

        double averageServiceTime = procStmtsServiceDemandList.getServiceDemandForRelation(relation);

        // lambda is equal to the inverse of mean service time(demand) calculated
        lambda.addContent(generateValueElem(String.format("%10f", 1 / averageServiceTime)));

        distrPar.addContent(lambda);

        return distrPar;

    }

    private Element generateExponentialSubParam() {
        return generateSubParam(JMTExponentialClasspath, XML_A_EXPONENTIAL);
    }

    private Element generateNumVisitsParamElem() {

        //Element dropStrats = generateGenericPerTransactionStrat(STRINGCLASSPATH, XML_A_DROPSTRATEGIES, generateNumberOfVisitsElem());
        Element numVisits = generateNewParam(INTCLASSPATH, CLASSNAME_NUMVISITS, true);

        for (Transaction t : transactions) {

            // init visits=0;
            double visits = 0;
            // for all procStmts in t
            for (IProceduralStatement proceduralStatement : t.getProceduralStatements()) {

                visits += findVisits(proceduralStatement);
            }
            // multiply by 100 to obtain visit ratios (because probabilities are accurate to 2dp)
            visits *= 100;
            //end for
            Element tRefClass = generateNewRefClassElem(t.getTransactionName());
            // create visits element
            //Element numVisitsForTransaction = generateNumberOfVisitsElem(visits);

            //using service demand in simulations, so all visits = 1
            Element numVisitsForTransaction = generateNumberOfVisitsElem(1);

            numVisits.addContent(tRefClass);
            numVisits.addContent(numVisitsForTransaction);
        }

        return numVisits;

    }


    private double findVisits(IProceduralStatement proceduralStatement) {

        double visits = 0;
        // if(loop) then
        if (proceduralStatement instanceof Loop) {

            Loop loop = (Loop) proceduralStatement;
            // visits += numIter * findVisits(loop.body)
            visits += loop.getNumIterations() * findVisits(loop.getLoopContents());
        } else if (proceduralStatement instanceof Branch) {
            // else if (branch) then visits += max(findVisits(trueB),findVisits(falseB)) and assume only
            // one of truB and falseB is visited
            Branch branch = (Branch) proceduralStatement;
            double trueP = branch.getTrueBranchExecProbability();
            double falseP = 1 - trueP;
            double trueVisits = findVisits(branch.getTrueStatement());
            double falseVisits = findVisits(branch.getFalseStatement());
            visits += (trueP * trueVisits) + (falseP * falseVisits);
        } else {
            // else visits += sum(findvisits(q[i])
            SQLQuerySequence sequence = (SQLQuerySequence) proceduralStatement;

            for (SQLQuery query : sequence.getStatementSequence()) {
                visits += findVisitsForSQLQuery(query);
            }
        }

        return Double.valueOf(dpFormat.format(visits));

    }

    private double findVisitsForSQLQuery(SQLQuery query) {

        return (query.getRelation().getFirst().equals(relation)) ? 1 : 0;

    }

    private Element generateNumberOfVisitsElem(double visits) {

        Element subParam = generateSubParam(INTCLASSPATH, CLASSNAME_NUMVISITS);
        int visitsInt = (int) visits;
        Element value = generateValueElem(Integer.toString(visitsInt));
        subParam.addContent(value);
        return subParam;

    }

    private Element generateMaxJobs() {

        Element maxJobs = generateNewParam(INTCLASSPATH, XML_A_GLOBALCONSTRAINT_MAXJOBS, false);

        // VALUE
        Element maxJobsValue = new Element(XML_E_PARAMETER_VALUE);
        maxJobsValue.addContent("1");

        maxJobs.addContent(maxJobsValue);
        return maxJobs;
    }
}
